package me.chan.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by JanGin on 2017/6/12.
 */
public class ClassUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

    /**
     * get the class loader
     * @return
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * load the class with the specified className
     * @param className
     * @return
     */
    public static Class<?> loadClass(String className) {
        Class<?> clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            LOGGER.error("load class failure {}", ex);
            throw new RuntimeException(ex);
        }
        return clazz;
    }

    /**
     * load the class with the specified className, isInitialized tells whether the class must be initialized
     * @param className
     * @param isInitialized
     * @return
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> clazz;
        try {
            clazz = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException ex) {
            LOGGER.error("load class failure {}", ex);
            throw new RuntimeException(ex);
        }
        return clazz;
    }

    /**
     * obtains all the classes under the specified package
     * @param packageName
     * @return
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String packagePath = protocol.replaceAll("%20", "");

                    addClasses(classSet, packagePath, packageName);

                } else if ("jar".equals(protocol)) {
                    JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                    if (jarURLConnection != null) {
                        JarFile jarFile = jarURLConnection.getJarFile();
                        if (jarFile != null) {
                            Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                            while (jarEntryEnumeration.hasMoreElements()) {
                                JarEntry jarEntry = jarEntryEnumeration.nextElement();
                                String jarEntryName = jarEntry.getName();
                                if (jarEntryName.endsWith(".class")) {
                                    String className = jarEntryName.substring(0,
                                            jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                    addClass(classSet, className);
                                }

                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("get class set failure", e);
        }
        return classSet;
    }


    private static void addClasses(Set<Class<?>> classSet, String packagePath, final String packageName) {
        File[] files= new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
               return (pathname.isFile() && pathname.getName().endsWith(".class")) || pathname.isDirectory();
            }
        });

        for(File file : files){
            String fileName = file.getName();
            if (file.isFile()) {
               String fileClassName = fileName.substring(0, fileName.lastIndexOf("."));
               if (StringUtils.isNotEmpty(fileClassName)) {

                   fileClassName = packageName + "." + fileClassName;
               }
               /**  //TODO need to be refactored
               try {
                   Class<?> clazz = Class.forName(fileClassName);
                   classSet.add(clazz);

               } catch (ClassNotFoundException e) {
                   LOGGER.error("could not load the class {}", e);
               }*/
                //refactor
                addClass(classSet, fileClassName);

           } else {
                String subFilePath = fileName;
               if (StringUtils.isNotEmpty(packagePath)) {
                   String subPackagePath = packagePath + "/" + subFilePath;
                   String subPackageName = fileName;
                   if (StringUtils.isNotEmpty(packageName)) {
                       subPackageName = packageName + "." + subPackageName;
                   }
                   addClasses(classSet, subPackagePath, subPackageName);
               }

           }
        }
    }


    private static void addClass(Set<Class<?>> classSet, String className) {
        Class<?> clazz = loadClass(className, false);
        classSet.add(clazz);
    }
}
